﻿namespace MicroCloud.Authorization
{
    #region "功能信息处理基类"
    /// <summary>
    /// 功能信息处理基类
    /// </summary>
    /// <typeparam name="TFunction">功能信息类型</typeparam>
    public abstract class FunctionHandlerBase<TFunction> : IFunctionHandler
        where TFunction : class, IEntity<long>, IFunction, new()
    {
        /// <summary>
        /// 功能信息
        /// </summary>
        private static TFunction[] _functions = [];

        #region "属性"
        //私有属性
        #region "获取 功能信息集合"
        /// <summary>
        /// 获取 功能信息集合
        /// </summary>
        private static List<TFunction> Functions { get; } = [];
        #endregion
        //受保护属性
        #region "获取 服务提供者对象"
        /// <summary>
        /// 获取 服务提供者对象
        /// </summary>
        protected IServiceProvider ServiceProvider { get; }
        #endregion
        #region "获取 日志对象"
        /// <summary>
        /// 获取 日志对象
        /// </summary>
        protected ILogger Logger => ServiceProvider.GetLogger(GetType());
        #endregion

        #endregion

        #region "构造函数"
        #region "初始化一个功能信息处理基类的新实例"
        /// <summary>
        /// 初始化一个功能信息处理基类 <see cref="FunctionHandlerBase{TFunction}"/> 的新实例
        /// </summary>
        /// <param name="serviceProvider">服务提供者</param>
        protected FunctionHandlerBase(IServiceProvider serviceProvider)
        {
            ServiceProvider = serviceProvider;
        }
        #endregion

        #endregion

        #region "方法"
        #region "从程序集中获取功能信息(如MVC的Controller-Action)"
        /// <summary>
        /// 从程序集中获取功能信息
        /// <para>如MVC的Controller-Action</para>
        /// </summary>
        public void Initialize()
        {
            Type[] functionTypes = GetAllFunctionTypes();
            _functions = GetFunctions(functionTypes);
            Logger.LogInformation("初始化功能信息，获取到 {length} 个功能信息。", _functions.Length);

            ServiceProvider.ExecuteScopedWork(provider =>
            {
                SyncToDatabase(provider, _functions);
            });

            RefreshCache();
        }
        #endregion
        #region "查找指定编号的功能信息"
        /// <summary>
        /// 查找指定编号的功能信息
        /// </summary>
        /// <param name="functionId">功能编号</param>
        /// <returns>功能信息</returns>
        public IFunction GetFunction(long functionId)
        {
            if (Functions.Count == 0)
            {
                RefreshCache();
            }

            return Functions.FirstOrDefault(m => m.Id == functionId);
        }
        #endregion
        #region "查找指定条件的功能信息"
        /// <summary>
        /// 查找指定条件的功能信息
        /// </summary>
        /// <param name="area">区域</param>
        /// <param name="controller">控制器</param>
        /// <param name="action">功能方法</param>
        /// <returns>功能信息</returns>
        public IFunction GetFunction(string area, string controller, string action)
        {
            if (Functions.Count == 0)
            {
                RefreshCache();
            }

            return Functions.FirstOrDefault(m =>
                string.Equals(m.Area.ReplaceNull(), area.ReplaceNull(), StringComparison.OrdinalIgnoreCase)
                && string.Equals(m.Controller.ReplaceNull(), controller.ReplaceNull(), StringComparison.OrdinalIgnoreCase)
                && string.Equals(m.Action.ReplaceNull(), action.ReplaceNull(), StringComparison.OrdinalIgnoreCase));
        }
        #endregion
        #region "刷新功能信息缓存"
        /// <summary>
        /// 刷新功能信息缓存
        /// </summary>
        public void RefreshCache()
        {
            ServiceProvider.ExecuteScopedWork(provider =>
            {
                Functions.Clear();
                Functions.AddRange(GetFromDatabase(provider));
                Logger.LogInformation("刷新功能信息缓存，从数据库获取到 {count} 个功能信息。", Functions.Count);
            });
        }
        #endregion
        #region "清空功能信息缓存"
        /// <summary>
        /// 清空功能信息缓存
        /// </summary>
        public void ClearCache()
        {
            Functions.Clear();
        }
        #endregion

        #endregion

        #region "受保护的方法"
        #region "从功能类型中获取功能信息"
        /// <summary>
        /// 从功能类型中获取功能信息
        /// </summary>
        /// <param name="functionTypes">功能类型集合</param>
        /// <returns></returns>
        protected virtual TFunction[] GetFunctions(Type[] functionTypes)
        {
            List<TFunction> functions = [];
            foreach (Type type in functionTypes.OrderBy(m => m.FullName))
            {
                TFunction controller = GetFunction(type);
                if (controller == null || type.HasAttribute<NonFunctionAttribute>())
                {
                    continue;
                }

                if (!HasPickup(functions, controller))
                {
                    functions.Add(controller);
                    Logger.LogDebug("提取功能信息 {controller}", controller);
                }

                List<MethodInfo> methods = GetMethodInfos(type).ToList();
                // 移除已被重写的方法
                MethodInfo[] overriddenMethodInfos = methods.Where(m => m.IsOverridden()).ToArray();
                foreach (MethodInfo overriddenMethodInfo in overriddenMethodInfos)
                {
                    methods.RemoveAll(m => m.Name == overriddenMethodInfo.Name && m != overriddenMethodInfo);
                }

                foreach (MethodInfo method in methods)
                {
                    TFunction action = GetFunction(controller, method);
                    if (action == null)
                    {
                        continue;
                    }

                    if (IsIgnoreMethod(action, method, functions))
                    {
                        continue;
                    }

                    if (HasPickup(functions, action))
                    {
                        continue;
                    }

                    functions.Add(action);
                    Logger.LogDebug("提取功能信息 {action}", action);
                }
            }

            return functions.ToArray();
        }
        #endregion
        #region "重写以判断指定功能信息是否已提取过"
        /// <summary>
        /// 重写以判断指定功能信息是否已提取过
        /// </summary>
        /// <param name="functions">已提取功能信息集合</param>
        /// <param name="function">要判断的功能信息</param>
        /// <returns></returns>
        protected virtual bool HasPickup(List<TFunction> functions, TFunction function)
        {
            return functions.Any(m =>
                string.Equals(m.Area.ReplaceNull(), function.Area.ReplaceNull(), StringComparison.OrdinalIgnoreCase)
                && string.Equals(m.Controller.ReplaceNull(), function.Controller.ReplaceNull(), StringComparison.OrdinalIgnoreCase)
                && string.Equals(m.Action.ReplaceNull(), function.Action.ReplaceNull(), StringComparison.OrdinalIgnoreCase)
                && string.Equals(m.Name.ReplaceNull(), function.Name.ReplaceNull(), StringComparison.OrdinalIgnoreCase));
        }
        #endregion
        #region "重写以实现功能信息查找"
        /// <summary>
        /// 重写以实现功能信息查找
        /// </summary>
        /// <param name="functions">功能信息集合</param>
        /// <param name="area">区域名称</param>
        /// <param name="controller">类型名称</param>
        /// <param name="action">方法名称</param>
        /// <param name="name">功能名称</param>
        /// <returns></returns>
        protected virtual TFunction GetFunction(IEnumerable<TFunction> functions, string area, string controller, string action, string name)
        {
            return functions.FirstOrDefault(m =>
                string.Equals(m.Area.ReplaceNull(), area.ReplaceNull(), StringComparison.OrdinalIgnoreCase)
                && string.Equals(m.Controller.ReplaceNull(), controller.ReplaceNull(), StringComparison.OrdinalIgnoreCase)
                && string.Equals(m.Action.ReplaceNull(), action.ReplaceNull(), StringComparison.OrdinalIgnoreCase)
                && string.Equals(m.Name.ReplaceNull(), name.ReplaceNull(), StringComparison.OrdinalIgnoreCase));
        }
        #endregion
        #region "重写以实现是否忽略指定方法的功能信息"
        /// <summary>
        /// 重写以实现是否忽略指定方法的功能信息
        /// </summary>
        /// <param name="action">要判断的功能信息</param>
        /// <param name="method">功能相关的方法信息</param>
        /// <param name="functions">已存在的功能信息集合</param>
        /// <returns></returns>
        protected virtual bool IsIgnoreMethod(TFunction action, MethodInfo method, IEnumerable<TFunction> functions)
        {
            if (method.HasAttribute<NonFunctionAttribute>())
            {
                return true;
            }
            TFunction existing = GetFunction(functions, action.Area, action.Controller, action.Action, action.Name);
            return existing != null;
        }
        #endregion
        #region "将从程序集获取的功能信息同步到数据库中"
        /// <summary>
        /// 将从程序集获取的功能信息同步到数据库中
        /// </summary>
        /// <param name="scopedProvider">局部服务程序提供者</param>
        /// <param name="functions">程序集获取的功能信息</param>
        protected virtual void SyncToDatabase(IServiceProvider scopedProvider, TFunction[] functions)
        {
            Check.NotNull(functions, nameof(functions));
            if (functions.Length == 0)
            {
                return;
            }

            var repository = scopedProvider.GetService<IRepository<TFunction, long>>();
            if (repository == null)
            {
                Logger.LogWarning("初始化功能数据时，IRepository<,>的服务未找到，请初始化 EntityFrameworkCoreModule 模块");
                return;
            }

            IUnitOfWork unitOfWork = scopedProvider.GetUnitOfWork(true);

            if (!functions.CheckSyncByHash(scopedProvider, Logger, typeof(TFunction), GetType(), "功能数据", "初始化功能数据") && repository.QueryAsNoTracking().Any() && functions.Length > 0)
            {
                Logger.LogInformation("同步功能数据时，验证数据签名与上次相同，跳过同步。");
                return;
            }

            TFunction[] dbItems = repository.Query(null, false).ToArray();

            // 删除的功能
            TFunction[] removeItems = dbItems.Except(functions, EqualityHelper<TFunction>.CreateComparer(m => m.Area + m.Controller + m.Action)).ToArray();
            int removeCount = removeItems.Length;
            // todo：由于外键关联不能物理删除的实体，需要实现逻辑删除
            foreach (TFunction function in removeItems)
            {
                repository.Delete(function);
                Logger.LogDebug("删除功能信息 {function}", function);
            }

            // 新增的功能
            TFunction[] addItems = functions.Except(dbItems, EqualityHelper<TFunction>.CreateComparer(m => m.Area + m.Controller + m.Action)).ToArray();
            int addCount = addItems.Length;
            foreach (TFunction function in addItems)
            {
                repository.Insert(function);
                Logger.LogDebug("新增功能信息 {function}", function);
            }

            // 更新的功能信息
            int updateCount = 0;
            foreach (TFunction item in dbItems.Except(removeItems))
            {
                bool isUpdate = false;
                TFunction function;
                try
                {
                    function = functions.Single(m =>
                        string.Equals(m.Area.ReplaceNull(), item.Area.ReplaceNull(), StringComparison.OrdinalIgnoreCase)
                        && string.Equals(m.Controller.ReplaceNull(), item.Controller.ReplaceNull(), StringComparison.OrdinalIgnoreCase)
                        && string.Equals(m.Action.ReplaceNull(), item.Action.ReplaceNull(), StringComparison.OrdinalIgnoreCase));
                }
                catch (InvalidOperationException)
                {
                    var actionString = item.Action.IsMissing() ? "" : $"-{item.Action}";
                    throw new Exception(I18N.T("发现多个 {0} 重名的功能信息", $"{item.Area}-{item.Controller}{actionString}"));
                }

                if (function == null)
                {
                    continue;
                }

                if (!string.Equals(item.Name.ReplaceNull(), function.Name.ReplaceNull(), StringComparison.OrdinalIgnoreCase))
                {
                    item.Name = function.Name;
                    isUpdate = true;
                }

                if (item.IsAjax != function.IsAjax)
                {
                    item.IsAjax = function.IsAjax;
                    isUpdate = true;
                }

                if (!item.IsAccessTypeChanged && item.AccessType != function.AccessType)
                {
                    item.AccessType = function.AccessType;
                    isUpdate = true;
                }

                if (isUpdate)
                {
                    repository.Update(item);
                    updateCount++;
                    Logger.LogDebug("更新功能信息 {function}", function);
                }
            }

            unitOfWork.Commit();
            if (removeCount + addCount + updateCount > 0)
            {
                string msg = "刷新功能信息：";
                if (addCount > 0)
                {
                    msg += "添加 " + addCount + " 个功能信息。";
                }
                if (removeCount > 0)
                {
                    msg += "删除 " + removeCount + " 个功能信息。";
                }
                if (updateCount > 0)
                {
                    msg += "更新 " + updateCount + " 个功能信息。";
                }

                Logger.LogInformation("{msg}", msg);
            }
        }
        #endregion
        #region "从数据库获取最新功能信息"
        /// <summary>
        /// 从数据库获取最新功能信息
        /// </summary>
        /// <param name="scopedProvider"></param>
        /// <returns></returns>
        protected virtual TFunction[] GetFromDatabase(IServiceProvider scopedProvider)
        {
            var repository = scopedProvider.GetService<IRepository<TFunction, long>>();
            if (repository == null)
            {
                return Array.Empty<TFunction>();
            }

            TFunction[] functions = [.. repository.QueryAsNoTracking(null, false)];
            foreach (var function in _functions)
            {
                var func = functions.FirstOrDefault(m => string.Equals(m.Area.ReplaceNull(), function.Area.ReplaceNull(), StringComparison.OrdinalIgnoreCase)
                && string.Equals(m.Controller.ReplaceNull(), function.Controller.ReplaceNull(), StringComparison.OrdinalIgnoreCase)
                && string.Equals(m.Action.ReplaceNull(), function.Action.ReplaceNull(), StringComparison.OrdinalIgnoreCase));
                if (func != null)
                {
                    function.Id = func.Id;
                    function.Name = func.Name;
                    function.Area = func.Area;
                    function.Controller = func.Controller;
                    function.Action = func.Action;
                    function.IsController = func.IsController;
                    function.IsAjax = func.IsAjax;
                    function.AccessType = func.AccessType;
                    function.IsAccessTypeChanged = func.IsAccessTypeChanged;
                    function.AuditOperationEnabled = func.AuditOperationEnabled;
                    function.AuditEntityEnabled = func.AuditEntityEnabled;
                    function.CacheExpirationSeconds = func.CacheExpirationSeconds;
                    function.IsCacheSliding = func.IsCacheSliding;
                    function.IsSlaveDatabase = func.IsSlaveDatabase;
                    //function.ControllerType = func.ControllerType;
                    //function.Method = func.Method;
                }
            }
            return _functions;
        }
        #endregion

        #endregion

        #region "抽象方法"
        #region "获取所有功能类型"
        /// <summary>
        /// 获取所有功能类型
        /// </summary>
        /// <returns></returns>
        public abstract Type[] GetAllFunctionTypes();
        #endregion
        #region "查找指定功能的所有功能点方法"
        /// <summary>
        /// 查找指定功能的所有功能点方法  
        /// </summary>
        /// <param name="functionType">功能类型</param>
        /// <returns></returns>
        public abstract MethodInfo[] GetMethodInfos(Type functionType);
        #endregion
        #region "重写以实现从功能类型创建功能信息"
        /// <summary>
        /// 重写以实现从功能类型创建功能信息
        /// </summary>
        /// <param name="type">功能类型</param>
        /// <returns></returns>
        protected abstract TFunction GetFunction(Type type);
        #endregion
        #region "重写以实现从方法信息中创建功能信息"
        /// <summary>
        /// 重写以实现从方法信息中创建功能信息
        /// </summary>
        /// <param name="typeFunction">类功能信息</param>
        /// <param name="method">方法信息</param>
        /// <returns></returns>
        protected abstract TFunction GetFunction(TFunction typeFunction, MethodInfo method);
        #endregion

        #endregion

    }
    #endregion

}
